﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;

namespace EfemSim
{
    class Efem
    {
        TcpListener listener;
        ConcurrentQueue<SocketDataParam> dataParams;
        List<Socket> sockets;
        Mutex mutex;
        Thread threadAnalyser;
        Dictionary<string, BaseAnalyser> analysers;
        volatile bool running;       // 仿真运行中
        volatile bool hold;
        volatile bool ems;
        volatile bool abort;
        volatile bool moving;
        volatile ushort delay;

        bool[] loadLocks;
        LoadPort[] loadPorts;
        Aligner aligner;
        Robot robot;
        SystemInputIO systemInput;
        SystemOutputIO systemOutput;


        public Efem()
        {
            this.mutex = new Mutex();
            this.sockets = new List<Socket>();
            this.delay = 10;
            this.systemInput = (SystemInputIO)0x7f;

            this.loadLocks = new bool[3];
            this.loadPorts = new LoadPort[2];
            for (int i = 0; i < this.loadPorts.Length; i++)
            {
                this.loadPorts[i] = new LoadPort() { Index = i + 1 };
                this.loadPorts[i].LoadPortMappingChanged += this.OnLoadPortMappingChanged;
            }

            this.aligner = new Aligner() { Index = 1 };
            this.robot = new Robot() { Index = 1 };

            this.analysers = new Dictionary<string, BaseAnalyser>()
            {
                { "MOV", new MovAnalyser() },
                { "GET", new GetAnalyser() },
                { "SET", new SetAnalyser() },
                { "ACK", new AckAnalyser() },
            };
        }


        #region 对外接口

        public void Start(IPAddress ip, int port)
        {
            if (this.listener != null)
            {
                throw new InvalidOperationException("running");
            }
            for (int i = 0; i < this.loadPorts.Length; i++)
            {
                this.loadPorts[i].LoadPortIoChanged += this.OnLoadPortIoChanged;
            }
            this.listener = new TcpListener(ip, port);
            this.listener.Start();
            this.listener.BeginAcceptTcpClient(this.AcceptTcpClient, this.listener);

            this.running = true;
            this.dataParams = new ConcurrentQueue<SocketDataParam>();
            (this.threadAnalyser = new Thread(this.DataAnalyser) { IsBackground = true }).Start();
        }

        public void Stop()
        {
            this.running = false;
            this.listener.Stop();
            this.listener = null;

            this.threadAnalyser.Join();
        }

        public void InjectError(int tag)
        {
            var io = (SystemInputIO)0x7f;
            switch (tag)
            {
                case 1:
                    io = (SystemInputIO)0x7c;
                    break;
                case 2:
                    io = (SystemInputIO)0x73;
                    break;
                case 3:
                    io = (SystemInputIO)0x3f;
                    break;
                case 0:
                default:
                    break;
            }
            this.systemInput = io;
            if (!this.running) return;
            this.OnSystemIoChanged();
        }

        public void InjectEvent(string name, string value) => this.RaiseEvent($"EVT:STATE/{name}/{value}");

        public void SetCarrier(int index, bool has)
        {
            if (has) this.loadPorts[index - 1].AddTag(LoadPortInputIO.CarrierPlaced);
            else this.loadPorts[index - 1].RemoveTag(LoadPortInputIO.CarrierPlaced);
        }
        public void SetAdapter(int index, bool has) => this.loadPorts[index - 1].HasAdapter = has;

        #endregion

        bool HasError(out string msg)
        {
            msg = null;
            if (((int)this.systemInput & 0x3) != 3)
            {
                msg = "VAC";
                return true;
            }
            if (((int)this.systemInput & 12) != 12)
            {
                msg = "AIR";
                return true;
            }
            if (((int)this.systemInput & 0x40) != 0x40)
            {
                msg = "VAC";
                return true;
            }
            return false;
        }

        #region  事件相关

        public event StoppedEventHandler Stopped;
        public event MessageArrivedEventHandler MessageArrived;
        public event SystemIoChangedEventHandler SystemIoChanged;
        void OnListenerErrorOrStopped(bool errorOrStop)
        {
            this.mutex.WaitOne();
            foreach (var i in this.sockets)
            {
                i.Close();
                i.Dispose();
            }
            this.sockets.Clear();
            this.mutex.ReleaseMutex();
            for (int i = 0; i < this.loadPorts.Length; i++)
            {
                this.loadPorts[i].LoadPortIoChanged -= this.OnLoadPortIoChanged;
            }
            this.Stopped?.Invoke(errorOrStop);
        }
        void OnMessageArrived(string msg, bool? sent = true) => this.MessageArrived?.Invoke(sent, msg);
        void OnSystemIoChanged()
        {
            this.RaiseEvent($"EVT:SIGSTAT/SYSTEM/{(int)this.systemInput:X8}/{(int)this.systemOutput:X8}");
            this.SystemIoChanged?.Invoke(this.systemInput, this.systemOutput);
        }

        //public event AlignerInitializedEventHandler AlignerInitialized;
        //public event AlignerOriginatedEventHandler AlignerOriginated;
        public event AlignerMovingEventHandler AlignerMoving;
        public event AlignerRotatingEventHandler AlignerRotating;
        //void OnAlignerInitialized() => this.AlignerInitialized?.Invoke();
        //void OnAlignerOriginated() => this.AlignerOriginated?.Invoke();
        void OnAlignerMoving(float rate) => this.AlignerMoving?.Invoke(rate);
        void OnAlignerRatating(float angle) => this.AlignerRotating?.Invoke(angle);

        //public event LoadPortInitializedEventHandler LoadPortInitialized;
        //public event LoadPortOriginatedEventHandler LoadPortOriginated;
        public event LoadPortLockedEventHandler LoadPortLocked;
        public event LoadPortDockedEventHandler LoadPortDocked;
        public event LoadPortDockingEventHandler LoadPortDocking;
        public event LoadPortOpenedEventHandler LoadPortOpened;
        public event LoadPortOpeningEventHandler LoadPortOpening;
        public event LoadPortMappingChangedEventHandler LoadPortMappingChanged;
        public event LoadPortIoChangedEventHandler LoadPortIoChagned;

        //void OnLoadPortInitialized(int index) => this.LoadPortInitialized?.Invoke(index);
        //void OnLoadPortOriginated(int index) => this.LoadPortOriginated?.Invoke(index);
        void OnLoadPortLocked(int index, bool locked) => this.LoadPortLocked?.Invoke(index, locked);
        void OnLoadPortDocked(int index, bool docked) => this.LoadPortDocked?.Invoke(index, docked);
        void OnLoadPortDocking(int index, bool dock, float rate) => this.LoadPortDocking?.Invoke(index, dock, rate);
        void OnLoadPortOpened(int index, bool opened) => this.LoadPortOpened?.Invoke(index, opened);
        void OnLoadPortOpening(int index, bool open, float rate) => this.LoadPortOpening?.Invoke(index, open, rate);
        void OnLoadPortMappingChanged(int index, bool[] slots)
        {
            this.RaiseEvent($"EVT:MAPDT/P{index}/{string.Join("", from i in slots select i ? "1" : "0")}");
            this.LoadPortMappingChanged?.Invoke(index, slots);
        }
        void OnLoadPortIoChanged(int index, LoadPortInputIO input, LoadPortOutputIO output)
        {
            this.RaiseEvent($"EVT:SIGSTAT/P{index}/{(int)input:X8}/{(int)output:X8}");
            this.LoadPortIoChagned?.Invoke(index, input, output);
        }

        public event AxisYMovedEventHandler AxisYMoved;
        void OnAxisYMoved(bool wafer1, bool wafer2, float rate) => this.AxisYMoved?.Invoke(wafer1, wafer2, rate);

        /*
         * 以下事件为 Robot 与各部件的运动事件
         */

        // aligner 运动事件
        public event RobotGotoAlignerEventHandler RobotGotoAligner;
        public event ArmGoInsideAlignerEventHandler ArmGoInsideAligner;
        public event AlignerWaferGotEventHandler AlignerWaferGot;
        void OnRobotGotoAligner(int arm, bool wafer1, bool wafer2, float rate) => this.RobotGotoAligner?.Invoke(arm, wafer1, wafer2, rate);
        void OnArmGoInsiderAligner(int arm, bool wafer1, bool wafer2, float rate) => this.ArmGoInsideAligner?.Invoke(arm, wafer1, wafer2, rate);
        void OnAlignerWaferGot(int arm, bool got) => this.AlignerWaferGot?.Invoke(arm, got);

        // load port 运动事件
        public event RobotGotoLoadPortEventHandler RobotGotoLoadPort;
        public event ArmGoInsideLoadPortEventHandler ArmGoInsideLoadPort;
        public event LoadPortWaferGotEventHandler LoadPortWaferGot;
        void OnRobotGotoLoadPort(int arm, bool wafer1, bool wafer2, float rate) => this.RobotGotoLoadPort?.Invoke(arm, wafer1, wafer2, rate);
        void OnArmGoInsideLoadPort(int lp, int arm, bool wafer1, bool wafer2, float rate) => this.ArmGoInsideLoadPort?.Invoke(lp, arm, wafer1, wafer2, rate);
        void OnLoadPortWaferGot(int lp, int arm, int slot, bool got) => this.LoadPortWaferGot?.Invoke(lp, slot, arm, got);

        // stage 运动事件
        public event RobotGotoStageEventHandler RobotGotoStage;
        public event ArmGoInsideStageEventHandler ArmGoInsideStage;
        public event StageWaferGotEventHandler StageWaferGot;
        void OnRobotGotoStage(int stage, int arm, bool wafer1, bool wafer2, float rate) => this.RobotGotoStage?.Invoke(stage, arm, wafer1, wafer2, rate);
        void OnArmGoInsideStage(int stage, int arm, bool wafer1, bool wafer2, float rate) => this.ArmGoInsideStage?.Invoke(stage, arm, wafer1, wafer2, rate);
        void OnStageWaferGot(int stage, int arm, bool got) => this.StageWaferGot?.Invoke(stage, arm, got);

        #endregion

        #region 通讯

        // 监听 socket 链接
        void AcceptTcpClient(IAsyncResult ia)
        {
            if (!this.running)
            {
                this.OnListenerErrorOrStopped(true);
                return;
            }
            try
            {
                var socket = this.listener.EndAcceptTcpClient(ia);
                this.OnMessageArrived($"{socket.Client.RemoteEndPoint} connected", null);
                var sp = this.BeginReceive(new SocketDataParam(new SocketState() { Socket = socket.Client }));
                new Thread(this.EstablishClient) { IsBackground = true }.Start(sp.SocketState);
                this.mutex.WaitOne();
                this.sockets.Add(socket.Client);
                this.mutex.ReleaseMutex();
                this.listener.BeginAcceptTcpClient(this.AcceptTcpClient, this.listener);
            }
            catch
            {
                this.OnListenerErrorOrStopped(true);
            }
        }

        SocketDataParam BeginReceive(SocketDataParam socket)
        {
            SocketDataParam sp = new SocketDataParam(socket.SocketState);
            sp.Buffer = new byte[256];
            socket.SocketState.Socket.BeginReceive(sp.Buffer, 0, 256, SocketFlags.None, this.ReceiveClientData, sp);
            return sp;
        }

        // 通讯建立协议
        void EstablishClient(object obj)
        {
            var sp = obj as SocketState;
            var socket = sp.Socket;

            byte[] data = Encoding.ASCII.GetBytes("INF:READY/COMM;\r");
            while (this.running && !sp.Established && socket != null && socket.Connected)
            {
                this.OnMessageArrived("INF:READY/COMM");
                socket.Send(data, 0, data.Length, SocketFlags.None, out SocketError code);
                Thread.Sleep(1500);
            }
        }

        // 收到 socket 数据
        void ReceiveClientData(IAsyncResult ia)
        {
            if (!this.running) return;
            var sp = ia.AsyncState as SocketDataParam;
            try
            {
                sp.Count = sp.SocketState.Socket.EndReceive(ia, out SocketError se);
                if (sp.Count == 0 || se != SocketError.Success)
                {
                    this.OnSocketClosed(sp);
                    return;
                }
                this.dataParams.Enqueue(sp);
                this.BeginReceive(sp);
            }
            catch
            {
                this.OnSocketClosed(sp);
            }
        }
        void OnSocketClosed(SocketDataParam sp)
        {
            this.OnMessageArrived($"{sp.SocketState.Socket.RemoteEndPoint} disconnected", null);
            sp.SocketState.Socket.Dispose();
            this.mutex.WaitOne();
            this.sockets.Remove(sp.SocketState.Socket);
            this.mutex.ReleaseMutex();
        }

        // 数据分析线程
        void DataAnalyser()
        {
            while (this.running)
            {
                if (!this.dataParams.TryDequeue(out SocketDataParam sp))
                {
                    Thread.Sleep(5);
                    continue;
                }
                new Thread(this.AnalysisData) { IsBackground = true }.Start(sp);
            }
            this.OnListenerErrorOrStopped(false);
        }

        // 分析数据
        void AnalysisData(object obj)
        {
            var sp = obj as SocketDataParam;
            string cmd = Encoding.ASCII.GetString(sp.Buffer, 0, sp.Count);
            try { cmd = cmd.Substring(0, cmd.LastIndexOf(';')); }
            catch
            {
                this.SendResponse(sp.SocketState.Socket, $"NAK:PARAM_NG|{cmd}");
                return;
            }
            this.OnMessageArrived(cmd, false);
            try
            {
                int idx = cmd.IndexOf(':');
                this.analysers[cmd.Substring(0, idx)].Analysis(this, sp.SocketState, cmd.Substring(idx + 1));
            }
            catch
            {
                this.SendResponse(sp.SocketState.Socket, $"NAK:PARAM_NG|{cmd}");
            }
        }

        #endregion

        void RaiseEvent(string msg)
        {
            this.OnMessageArrived(msg);
            byte[] data = Encoding.ASCII.GetBytes($"{msg};\n");
            this.mutex.WaitOne();
            try
            {
                foreach (var s in this.sockets)
                {
                    s.Send(data);
                }
            }
            finally
            {
                this.mutex.ReleaseMutex();
            }
        }

        void SendResponse(Socket socket, string resp)
        {
            if (!socket.Connected) return;
            try
            {
                socket.Send(Encoding.ASCII.GetBytes($"{resp};\n"));
                this.OnMessageArrived(resp);
            }
            catch { }
        }
        // 通讯建立后，发点事件
        void OnComEstablished()
        {
            this.OnSystemIoChanged();
            foreach (var lp in this.loadPorts) lp.NotifyIOChagned();
        }

        #region EFEM 部件

        enum Stages
        {
            Aligner,
            LoadPort,
            Stage,
        }

        class EfemComponent
        {
            public string Name;

            public int Index;
            public bool Initialized;
            public bool Originated;
            public volatile bool Moving;
            public bool[] Slots;

            public EfemComponent(string name) => this.Name = name;

            public virtual bool Initialize(Efem efem)
            {
                if (this.Initialized) return false;
                Thread.Sleep(1000);
                this.Initialized = true;
                this.Originated = false;
                return false;
            }
            public virtual bool Originate(Efem efem)
            {
                this.Originated = true;
                return false;
            }

            protected bool MoveSteps<T>(Efem efem, T arg, ushort steps, ushort forward, ref ushort oldStep, Action<Efem, T> progress) where T : MotionArgs
            {
                float cent = 1f / steps;
                float rate = cent * oldStep;
                ushort step = 1;
                ushort sum;

                if (forward > oldStep)
                {
                    sum = forward;
                    sum -= oldStep;
                }
                else
                {
                    cent = -cent;
                    step = 65535;
                    sum = oldStep;
                    sum -= forward;
                }
                if (progress == null) progress = this.NullProgress;

                ushort cnt = 0;
                this.Moving = true;
                while (cnt < sum)
                {
                    while (efem.hold)
                    {
                        Thread.Sleep(5);
                        if (efem.ems || !efem.running || efem.HasError(out _))
                        {
                            this.Moving = false;
                            return true;
                        }
                        if (efem.abort) break;
                    }
                    if (!efem.running || efem.HasError(out _))
                    {
                        this.Moving = false;
                        return true;
                    }

                    cnt++;
                    rate += cent;
                    oldStep += step;
                    arg.Rate = rate;
                    progress(efem, arg);
                    Thread.Sleep(efem.delay);
                }

                if (efem.abort)
                {
                    while (cnt > 0)
                    {
                        if (efem.ems || !efem.running || efem.HasError(out _))
                        {
                            this.Moving = false;
                            return true;
                        }

                        rate -= cent;
                        cnt--;
                        oldStep += step;
                        arg.Rate = rate;
                        progress(efem, arg);
                        Thread.Sleep(efem.delay);
                    }
                }

                return this.Moving = false;
            }
            void NullProgress<T>(Efem efem, T arg) where T : MotionArgs { }

            protected class MotionArgs
            {
                public float Rate;
            }
        }

        class LoadPort : EfemComponent
        {
            public bool Locked;
            public bool Docked;
            public bool Opened;
            public bool HasAdapter;

            ushort oldOriginate, oldDock, oldOpen;
            LoadPortInputIO input;
            LoadPortOutputIO output;

            public event LoadPortIoChangedEventHandler LoadPortIoChanged;
            public event LoadPortMappingChangedEventHandler LoadPortMappingChanged;

            public bool Placed => (this.input & LoadPortInputIO.CarrierPlaced) == LoadPortInputIO.CarrierPlaced;

            public LoadPort() : base("P") { }

            public void NotifyIOChagned() => this.LoadPortIoChanged?.Invoke(this.Index, this.input, this.output);
            public void NotifyMappingChanged() => this.LoadPortMappingChanged?.Invoke(this.Index, this.Slots);
            public void AddTag(LoadPortInputIO input)
            {
                this.input |= input;
                this.NotifyIOChagned();
            }
            public void RemoveTag(LoadPortInputIO input)
            {
                this.input &= ~input;
                this.NotifyIOChagned();
            }
            void AddTag(LoadPortOutputIO output)
            {
                this.output |= output;
                this.NotifyIOChagned();
            }
            void RemoveTag(LoadPortOutputIO output)
            {
                this.output &= ~output;
                this.NotifyIOChagned();
            }


            public override bool Originate(Efem efem)
            {
                if (!this.Initialized) return true;
                if (this.Moving) return true;
                if (this.Opened)
                {
                    if (this.Close(efem)) return true;
                }
                if (this.Docked)
                {
                    if (this.Undock(efem)) return true;
                }
                if (this.Locked)
                {
                    if (this.Unlock(efem)) return true;
                }
                if (this.MoveSteps(efem, new MotionArgs(), 200, 200, ref this.oldOriginate, null)) return true;
                if (efem.abort) return false;

                return base.Originate(efem);
            }
            public bool Lock(Efem efem)
            {
                if (!this.Initialized || !this.Originated || !this.Placed) return true;
                if (this.Locked) return false;
                this.Locked = true;
                efem.OnLoadPortLocked(this.Index, true);
                return false;
            }
            public bool Dock(Efem efem)
            {
                if (!this.Initialized || !this.Originated || this.Moving) return true;
                if (this.Docked) return false;
                if (!this.Locked)
                {
                    if (this.Lock(efem)) return true;
                }
                if (this.MoveSteps(efem, new MotionArgs(), 50, 50, ref this.oldDock, this.DockProgress)) return true;
                if (efem.abort) return false;
                this.Docked = true;
                efem.OnLoadPortDocked(this.Index, true);
                this.AddTag(LoadPortOutputIO.Dock);
                return false;
            }
            void DockProgress(Efem efem, MotionArgs arg) => efem.OnLoadPortDocking(this.Index, true, arg.Rate);
            public bool Open(Efem efem)
            {
                if (!this.Initialized || !this.Originated || this.Moving) return true;
                if (this.Opened) return false;
                if (!this.Docked)
                {
                    if (this.Dock(efem)) return true;
                    if (efem.abort) return false;
                }
                if (this.MoveSteps(efem, new MotionArgs(), 150, 150, ref this.oldOpen, this.OpenProgress)) return true;
                if (efem.abort) return false;
                if (this.Slots == null)
                {
                    this.Slots = new bool[25];
                    for (int i = 0; i < 25; i++) this.Slots[i] = true;
                }
                efem.OnLoadPortMappingChanged(this.Index, this.Slots);
                this.Opened = true;
                efem.OnLoadPortOpened(this.Index, true);
                return false;
            }
            void OpenProgress(Efem efem, MotionArgs arg) => efem.OnLoadPortOpening(this.Index, true, arg.Rate);
            public bool Close(Efem efem)
            {
                if (!this.Initialized || !this.Originated || this.Moving) return true;
                if (!this.Opened) return false;
                if (this.MoveSteps(efem, new MotionArgs(), 150, 0, ref this.oldOpen, this.CloseProgress)) return true;
                if (efem.abort) return false;
                this.Opened = false;
                efem.OnLoadPortOpened(this.Index, false);
                //efem.OnLoadPortMappingChanged(this.Index, this.Slots);
                return false;
            }
            void CloseProgress(Efem efem, MotionArgs arg) => efem.OnLoadPortOpening(this.Index, false, arg.Rate);
            public bool Undock(Efem efem)
            {
                if (!this.Initialized || !this.Originated || this.Moving) return true;
                if (!this.Docked) return false;
                if (this.Opened)
                {
                    if (this.Close(efem)) return true;
                    if (efem.abort) return false;
                }
                if (this.MoveSteps(efem, new MotionArgs(), 50, 0, ref this.oldDock, this.UndockProgress)) return true;
                if (efem.abort) return false;
                this.Docked = false;
                efem.OnLoadPortDocked(this.Index, false);
                this.RemoveTag(LoadPortOutputIO.Dock);
                return false;
            }
            void UndockProgress(Efem efem, MotionArgs arg) => efem.OnLoadPortDocking(this.Index, false, arg.Rate);
            public bool Unlock(Efem efem)
            {
                if (!this.Initialized || !this.Originated) return true;
                if (!this.Locked) return false;
                if (this.Opened)
                {
                    if (this.Close(efem)) return true;
                    if (efem.abort) return false;
                }
                if (this.Docked)
                {
                    if (this.Undock(efem)) return true;
                    if (efem.abort) return false;
                }
                this.Locked = false;
                efem.OnLoadPortLocked(this.Index, false);
                return false;
            }
            public bool Wafsh(Efem efem)
            {
                if (!this.Initialized || !this.Originated || !this.Opened || this.Moving) return true;
                ushort old = 0;
                if (this.MoveSteps(efem, new MotionArgs(), 100, 100, ref old, null)) return true;
                if (efem.abort) return false;
                efem.OnLoadPortMappingChanged(this.Index, this.Slots);
                return false;
            }
        }

        class Aligner : EfemComponent
        {
            public bool HasWafer { get => this.Slots[0]; set => this.Slots[0] = value; }
            public bool Clamped;

            float angle;
            short id;

            public Aligner() : base("ALIGN") => this.Slots = new bool[1];

            public override bool Originate(Efem efem)
            {
                if (!this.Initialized) return true;
                ushort oldMove = 0;
                if (this.MoveSteps(efem, new MotionArgs(), 200, 200, ref oldMove, this.Originating)) return true;
                if (efem.abort) return false;
                return base.Originate(efem);
            }
            void Originating(Efem efem, MotionArgs arg) => efem.OnAlignerMoving(arg.Rate);
            public bool Align(Efem efem)
            {
                if (!this.Initialized || !this.Originated || !this.Clamped || !this.HasWafer || this.Moving) return true;
                this.Clamped = true;
                ushort step = 0;
                if (this.MoveSteps(efem, new MotionArgs(), 180, 180, ref step, this.Aligning)) return true;
                if (efem.abort) return false;
                while (this.angle < 0) this.angle += 360f;
                this.angle %= 360;
                step = (ushort)(360 * this.angle / 1080);
                ushort idx = 0;
                if (this.MoveSteps(efem, new MotionArgs(), 180, step, ref idx, this.Aligning)) return true;
                return false;
            }
            void Aligning(Efem efem, MotionArgs arg) => efem.OnAlignerRatating((arg.Rate * 3600) % 360);
            public void SetAngle(float angle) => this.angle = angle;

            public string GetWaferID() => $"WFS-WAFER {this.id++:D3}";
        }

        class Robot : EfemComponent
        {
            public bool Wafer1 => this.Slots[0];
            public bool Wafer2 => this.Slots[1];
            public bool[] clamps;

            ushort axisY1;
            ushort axisY2;
            ushort axisX1;
            ushort axisX2;
            byte arm;
            Positions position;

            public Robot() : base("ROBOT")
            {
                this.Slots = new bool[2];
                this.clamps = new bool[2];
            }

            enum Positions
            {
                LLB_LLC,
                Origin_LLB_LLC_Door,

                LP2, LP2_Door, LP2_LP,

                LP1, LP1_Door, LP1_LP,

                LLA, LLA_Door, Aligner_Door,
                Aligner,
            }

            class RobotMotionArgs : MotionArgs
            {
                public byte Index;
            }

            bool GotoLine(Efem efem)
            {
                var arg = new RobotMotionArgs();
                switch (this.position)
                {
                    case Positions.LLA:
                        arg.Index = 1;
                        if (this.MoveBetweenLLA_Door(efem, arg, 0)) return true;
                        return this.MoveBetweenLlaDoor_AlignerDoor(efem, arg, 0);
                    case Positions.LLA_Door:
                        arg.Index = 1;
                        return this.MoveBetweenLlaDoor_AlignerDoor(efem, arg, 0);

                    case Positions.LP1_LP:
                        arg.Index = 1;
                        if (this.MoveBetweenLP_Door(efem, arg, 0, Positions.LP1_Door)) return true;
                        return this.MoveBetweenLPDoor_LP(efem, arg, 0, Positions.LP1);
                    case Positions.LP1_Door:
                        arg.Index = 1;
                        return this.MoveBetweenLPDoor_LP(efem, arg, 0, Positions.LP1);

                    case Positions.LP2_LP:
                        arg.Index = 2;
                        if (this.MoveBetweenLP_Door(efem, arg, 0, Positions.LP2_Door)) return true;
                        return this.MoveBetweenLPDoor_LP(efem, arg, 0, Positions.LP2);
                    case Positions.LP2_Door:
                        arg.Index = 2;
                        return this.MoveBetweenLPDoor_LP(efem, arg, 0, Positions.LP2);

                    case Positions.Aligner:
                        return this.MoveBetweenAligner_Door(efem, arg, 0);
                    case Positions.LLB_LLC:
                        arg.Index = 2;
                        return this.MoveBetweenLLBC_Door(efem, arg, 0);
                    default:
                        return false;
                }
            }

            #region 各段之间的移动

            // LLA <---> LLA_DOOR： arm
            bool MoveBetweenLLA_Door(Efem efem, RobotMotionArgs arg, ushort target)
            {
                if (this.MoveSteps(efem, arg, 100, target, ref this.axisX2, this.MoveInsideLLA)) return true;
                if (efem.abort) return false;
                this.position = target == 0 ? Positions.LLA_Door : Positions.LLA;
                return false;
            }
            void MoveInsideLLA(Efem efem, RobotMotionArgs arg) => efem.OnArmGoInsideStage(1, this.arm, this.Wafer1, this.Wafer2, arg.Rate);
            // LLA_DOOR <---> ALIGNER_DOOR： arm
            bool MoveBetweenLlaDoor_AlignerDoor(Efem efem, RobotMotionArgs arg, ushort target)
            {
                if (this.MoveSteps(efem, arg, 100, target, ref this.axisX1, this.MoveToLLA)) return true;
                if (efem.abort) return false;
                this.position = target == 0 ? Positions.Aligner_Door : Positions.LLA_Door;
                return false;
            }
            void MoveToLLA(Efem efem, RobotMotionArgs arg) => efem.OnRobotGotoStage(arg.Index, this.arm, this.Wafer1, this.Wafer2, arg.Rate);
            // LP <---> LP_DOOR： arm
            bool MoveBetweenLP_Door(Efem efem, RobotMotionArgs arg, ushort target, Positions poit)
            {
                if (this.MoveSteps(efem, arg, 100, target, ref this.axisX2, this.MoveInsideLoadPort)) return true;
                if (efem.abort) return false;
                this.position = poit;
                return false;
            }
            void MoveInsideLoadPort(Efem efem, RobotMotionArgs arg) => efem.OnArmGoInsideLoadPort(arg.Index, this.arm, this.Wafer1, this.Wafer2, arg.Rate);
            // LP_DOOR <---> LP
            bool MoveBetweenLPDoor_LP(Efem efem, RobotMotionArgs arg, ushort target, Positions poit)
            {
                if (this.MoveSteps(efem, arg, 100, target, ref this.axisX1, this.MoveToLoadPort)) return true;
                if (efem.abort) return false;
                this.position = poit;
                return false;
            }
            void MoveToLoadPort(Efem efem, RobotMotionArgs arg) => efem.OnRobotGotoLoadPort(this.arm, this.Wafer1, this.Wafer2, arg.Rate);
            // ALIGNER <---> ALIGNER_DOOR
            bool MoveBetweenAligner_Door(Efem efem, RobotMotionArgs arg, ushort target)
            {
                if (this.MoveSteps(efem, arg, 100, target, ref this.axisY2, this.MoveInsideAligner)) return true;
                if (efem.abort) return false;
                this.position = target == 0 ? Positions.Aligner_Door : Positions.Aligner;
                return false;
            }
            void MoveInsideAligner(Efem efem, RobotMotionArgs arg) => efem.OnArmGoInsiderAligner(this.arm, this.Wafer1, this.Wafer2, arg.Rate);
            // ALIGNER_DOOR <---> LP1
            bool MoveBetweenAlignerDoor_LP(Efem efem, RobotMotionArgs arg, ushort target)
            {
                if (this.MoveSteps(efem, arg, 400, target, ref this.axisY1, this.MoveToAligner)) return true;
                if (efem.abort) return false;
                this.position = target == 300 ? Positions.LP1 : Positions.Aligner_Door;
                return false;
            }
            void MoveToAligner(Efem efem, RobotMotionArgs arg) => efem.OnRobotGotoAligner(this.arm, this.Wafer1, this.Wafer2, arg.Rate);
            // LLB/C <---> LLB/C_DOOR
            bool MoveBetweenLLBC_Door(Efem efem, RobotMotionArgs arg, ushort target)
            {
                if (this.MoveSteps(efem, arg, 100, target, ref this.axisY2, this.MoveInsideStage)) return true;
                if (efem.abort) return false;
                this.position = target == 0 ? Positions.Origin_LLB_LLC_Door : Positions.LLB_LLC;
                return false;
            }
            void MoveInsideStage(Efem efem, RobotMotionArgs arg) => efem.OnArmGoInsideStage(arg.Index, this.arm, this.Wafer1, this.Wafer2, arg.Rate);
            // LLB/C_DOOR <---> LP2
            bool MoveBetweenLlbDoor_LP2(Efem efem, RobotMotionArgs arg, ushort target)
            {
                if (this.MoveSteps(efem, arg, 400, target, ref this.axisY1, this.MoveToStage)) return true;
                if (efem.abort) return false;
                this.position = target == 0 ? Positions.Origin_LLB_LLC_Door : Positions.LP2;
                return false;
            }
            void MoveToStage(Efem efem, RobotMotionArgs arg) => efem.OnRobotGotoStage(arg.Index, this.arm, this.Wafer1, this.Wafer2, arg.Rate);

            // Y 轴联动
            bool MoveAxisY(Efem efem, RobotMotionArgs arg, ushort target)
            {
                if (this.MoveSteps(efem, arg, 400, target, ref this.axisY1, this.MoveAxisY)) return true;
                this.position = target == 0 ? Positions.Origin_LLB_LLC_Door : target == 100 ? Positions.LP2 : Positions.LP1;
                return false;
            }
            void MoveAxisY(Efem efem, RobotMotionArgs arg) => efem.OnAxisYMoved(this.Wafer1, this.Wafer2, arg.Rate);

            #endregion

            void CheckClamp()
            {
                this.clamps[0] = this.Slots[0];
                this.clamps[1] = this.Slots[1];
            }

            public override bool Originate(Efem efem)
            {
                if (!this.Initialized) return true;
                this.CheckClamp();
                if (this.GotoLine(efem)) return true;
                var arg = new RobotMotionArgs();
                switch (this.position)
                {
                    case Positions.Origin_LLB_LLC_Door:
                        arg.Index = 2;
                        if (this.MoveBetweenLlbDoor_LP2(efem, arg, 100)) return true;
                        if (this.MoveAxisY(efem, arg, 0)) return true;
                        break;
                    case Positions.LP2:
                    case Positions.LP1:
                        if (this.MoveAxisY(efem, arg, 0)) return true;
                        break;
                    case Positions.Aligner_Door:
                        if (this.MoveBetweenAlignerDoor_LP(efem, arg, 300)) return true;
                        if (this.MoveAxisY(efem, arg, 0)) return true;
                        break;
                }
                if (efem.abort) return false;
                this.arm = 0;
                return base.Originate(efem);
            }

            public bool GotoAligner(Efem efem, int index, byte arm)
            {
                if (!this.Initialized || !this.Originated || this.Moving) return true;
                if (this.position == Positions.Aligner_Door && this.arm == arm) return false;
                this.CheckClamp();
                if (this.GotoLine(efem)) return true;

                var arg = new RobotMotionArgs();
                arg.Index = (byte)index;
                while (this.position != Positions.Aligner_Door || this.arm != arm)
                {
                    switch (this.position)
                    {
                        case Positions.Origin_LLB_LLC_Door:
                            if (this.MoveBetweenLlbDoor_LP2(efem, arg, 100)) return true;
                            break;
                        case Positions.LP2:
                            if (this.MoveAxisY(efem, arg, 300)) return true;
                            break;
                        case Positions.LP1:
                            this.arm = arm;
                            if (this.MoveBetweenAlignerDoor_LP(efem, arg, 400)) return true;
                            break;
                        case Positions.Aligner_Door:
                            if (this.MoveBetweenAlignerDoor_LP(efem, arg, 300)) return true;
                            break;
                    }
                }

                return false;
            }
            public bool GotoStage(Efem efem, int index, byte arm)
            {
                if (!this.Initialized || !this.Originated || this.Moving) return true;
                Positions target = index == 1 ? Positions.LLA_Door : Positions.Origin_LLB_LLC_Door;
                this.CheckClamp();
                if (this.position == target && this.arm == arm) return false;
                if (this.GotoLine(efem)) return true;

                var arg = new RobotMotionArgs();
                arg.Index = (byte)index;
                while (this.position != target || this.arm != arm)
                {
                    switch (this.position)
                    {
                        case Positions.Origin_LLB_LLC_Door:
                            if (index == 1 || this.arm != arm)
                            {
                                if (this.MoveBetweenLlbDoor_LP2(efem, arg, 100)) return true;
                            }
                            break;
                        case Positions.LP2:
                            if (index == 1)
                            {
                                if (this.MoveAxisY(efem, arg, 300)) return true;
                            }
                            else
                            {
                                this.arm = arm;
                                if (this.MoveBetweenLlbDoor_LP2(efem, arg, 0)) return true;
                            }
                            break;
                        case Positions.LP1:
                            if (index == 1)
                            {
                                this.arm = arm;
                                if (this.MoveBetweenAlignerDoor_LP(efem, arg, 400)) return true;
                            }
                            else
                            {
                                if (this.MoveAxisY(efem, arg, 100)) return true;
                            }
                            break;
                        case Positions.Aligner_Door:
                            if (index != 1 || this.arm != arm)
                            {
                                if (this.MoveBetweenAlignerDoor_LP(efem, arg, 300)) return true;
                                break;
                            }
                            if (this.MoveBetweenLlaDoor_AlignerDoor(efem, arg, 100)) return true;
                            break;
                    }
                }

                if (efem.abort) return false;
                this.position = target;
                return false;
            }
            public bool GotoLoadPort(Efem efem, int index, byte arm)
            {
                if (!this.Initialized || !this.Originated || this.Moving) return true;
                Positions target = index == 1 ? Positions.LP1_Door : Positions.LP2_Door;
                this.CheckClamp();
                if (this.position == target && this.arm == arm) return false;
                if (this.GotoLine(efem)) return true;

                while (this.position != target || this.arm != arm)
                {
                    switch (this.position)
                    {
                        case Positions.Origin_LLB_LLC_Door:
                            if (this.MoveBetweenLlbDoor_LP2(efem, new RobotMotionArgs() { Index = 2 }, 100)) return true;
                            break;
                        case Positions.LP2:
                            if (index == 1)
                            {
                                if (this.MoveAxisY(efem, new RobotMotionArgs(), 300)) return true;
                            }
                            else
                            {
                                this.arm = arm;
                                if (this.MoveBetweenLPDoor_LP(efem, new RobotMotionArgs(), 100, Positions.LP2_Door)) return true;
                            }
                            break;
                        case Positions.LP1:
                            if (index == 1)
                            {
                                this.arm = arm;
                                if (this.MoveBetweenLPDoor_LP(efem, new RobotMotionArgs(), 100, Positions.LP1_Door)) return true;
                            }
                            else
                            {
                                if (this.MoveAxisY(efem, new RobotMotionArgs(), 100)) return true;
                            }
                            break;
                        case Positions.Aligner_Door:
                            if (this.MoveBetweenAlignerDoor_LP(efem, new RobotMotionArgs(), 300)) return true;
                            break;
                    }
                }

                if (efem.abort) return false;
                this.position = target;
                return false;
            }

            bool PickPlace(Efem efem, byte arm, Stages stage, int index, int slot, bool got)
            {
                if (this.Moving) return true;
                var arg = new RobotMotionArgs();
                arg.Index = (byte)index;
                switch (stage)
                {
                    case Stages.Aligner:
                        if (got == !efem.aligner.HasWafer || got == this.Slots[arm - 1]) return true;
                        if (this.GotoAligner(efem, index, arm)) return true;
                        if (efem.abort) return false;
                        if (this.MoveBetweenAligner_Door(efem, arg, 100)) return true;
                        efem.OnAlignerWaferGot(arm, got);
                        this.Slots[arm - 1] = this.clamps[arm - 1] = got;
                        efem.aligner.Clamped = efem.aligner.HasWafer = !got;
                        if (this.MoveBetweenAligner_Door(efem, arg, 0)) return true;
                        return this.MoveBetweenAlignerDoor_LP(efem, arg, 300);

                    case Stages.LoadPort:
                        if (got == !efem.loadPorts[index - 1].Slots[slot - 1] || got == this.Slots[arm - 1]) return true;
                        if (this.GotoLoadPort(efem, index, arm)) return true;
                        if (efem.abort) return false;
                        if (this.MoveBetweenLP_Door(efem, arg, 100, index == 1 ? Positions.LP1 : Positions.LP2)) return true;
                        efem.OnLoadPortWaferGot(index, arm, slot, got);
                        this.Slots[arm - 1] = this.clamps[arm - 1] = got;
                        efem.loadPorts[index - 1].Slots[slot - 1] = !got;
                        efem.loadPorts[index - 1].NotifyMappingChanged();
                        if (this.MoveBetweenLP_Door(efem, arg, 0, index == 1 ? Positions.LP1_Door : Positions.LP2_Door)) return true;
                        return this.MoveBetweenLPDoor_LP(efem, arg, 0, index == 1 ? Positions.LP1 : Positions.LP2);

                    case Stages.Stage:
                        if (got != efem.loadLocks[index - 1] || got == this.Slots[arm - 1]) return true;
                        if (this.GotoStage(efem, index, arm)) return true;
                        if (efem.abort) return false;
                        if (index == 1)
                        {
                            if (this.MoveBetweenLLA_Door(efem, arg, 100)) return true;
                        }
                        else
                        {
                            if (this.MoveBetweenLLBC_Door(efem, arg, 100)) return true;
                        }
                        efem.OnStageWaferGot(index, arm, got);
                        efem.loadLocks[index - 1] = got;
                        this.Slots[arm - 1] = this.clamps[arm - 1] = got;
                        efem.loadLocks[index - 1] = !got;
                        if (index == 1)
                        {
                            if (this.MoveBetweenLLA_Door(efem, arg, 0)) return true;
                            if (this.MoveBetweenLlaDoor_AlignerDoor(efem, arg, 0)) return true;
                            return this.MoveBetweenAlignerDoor_LP(efem, arg, 300);
                        }
                        else
                        {
                            if (this.MoveBetweenLLBC_Door(efem, arg, 0)) return true;
                            return this.MoveBetweenLlbDoor_LP2(efem, arg, 100);
                        }

                    default:
                        throw new Exception();
                }
            }

            public bool Pick(Efem efem, byte arm, Stages stage, int index, int slot) => this.PickPlace(efem, arm, stage, index, slot, true);
            public bool Place(Efem efem, byte arm, Stages stage, int index, int slot) => this.PickPlace(efem, arm, stage, index, slot, false);
        }

        #endregion

        #region 内部类

        class SocketState
        {
            public Socket Socket;
            public volatile bool Established;
        }

        class SocketDataParam
        {
            public byte[] Buffer;
            public int Count;

            public SocketDataParam(SocketState state) => this.SocketState = state;

            public SocketState SocketState { get; private set; }
        }

        abstract class BaseAnalyser
        {
            Dictionary<string, Action<Efem, SocketState, string>> analysers;

            protected BaseAnalyser()
            {
                this.analysers = new Dictionary<string, Action<Efem, SocketState, string>>();
                var type = typeof(Action<Efem, SocketState, string>);
                var ms = this.GetType().GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                foreach (var m in ms)
                {
                    if (!m.IsPrivate) continue;
                    this.analysers[m.Name.Substring(3).ToUpper()] = Delegate.CreateDelegate(type, this, m) as Action<Efem, SocketState, string>;
                }
            }

            protected List<EfemComponent> GetEfemItems(Efem efem)
            {
                List<EfemComponent> l = new List<EfemComponent>();
                l.Add(efem.robot);
                l.Add(efem.aligner);
                l.AddRange(efem.loadPorts);
                return l;
            }

            public void Analysis(Efem efem, SocketState socket, string msgParam)
            {
                if (this.CheckCommunication(efem, socket, msgParam)) return;

                int idx = msgParam.IndexOf('/');
                string msg, arg = null;
                msg = msgParam;
                if (idx > 0)
                {
                    msg = msgParam.Substring(0, idx);
                    arg = msgParam.Substring(idx + 1);
                }

                this.Analysis(efem, socket, msg, arg);
            }

            protected virtual void Analysis(Efem efem, SocketState socket, string msg, string arg) => this.analysers[msg](efem, socket, arg);

            protected string Concat(string msg, string param) => param == null ? msg : string.Concat(msg, "/", param);

            // 检查通讯
            protected virtual bool CheckCommunication(Efem efem, SocketState socket, string msgParam)
            {
                if (!socket.Established)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{msgParam}|NOREADY");
                    return true;
                }
                return false;
            }

            // 检查是否初始化
            protected bool CheckEfemInitialized(Efem efem, EfemComponent item, SocketState socket, string msg, string param)
            {
                string err = this.Concat(msg, param);
                if (efem.ems)
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|EMS");
                    return true;
                }
                if (!item.Initialized)
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|NOINITCMPL/{item.Name}{item.Index}");
                    return true;
                }
                return false;
            }
            // 检查是否在 0 点
            protected bool CheckEfemItemOriginated(Efem efem, EfemComponent item, SocketState socket, string msg, string param)
            {
                string err = this.Concat(msg, param);
                if (efem.ems)
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|EMS");
                    return true;
                }
                if (!item.Initialized)
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|NOINITCMPL/{item.Name}{item.Index}");
                    return true;
                }
                if (!item.Originated)
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|NOORGCMPL/{item.Name}{item.Index}");
                    return true;
                }
                return false;
            }
            // 检查 LP 是否在 0 点
            protected LoadPort CheckLoadPortOriginated(Efem efem, SocketState socket, string param, out string cmd, out string msgOut, [CallerMemberName] string msg = null)
            {
                msg = msg.Substring(3).ToUpper();
                msgOut = msg;
                cmd = this.Concat(msg, param);
                byte idx;
                if (string.IsNullOrEmpty(param) || param[0] != 'P' || !byte.TryParse(param.Substring(1), out idx) || idx == 0 || idx > efem.loadPorts.Length)
                {
                    efem.SendResponse(socket.Socket, $"NAK:{cmd}|PARAM_NG");
                    return null;
                }
                var lp = efem.loadPorts[idx - 1];
                if (this.CheckEfemItemOriginated(efem, lp, socket, msg, param)) return null;
                if (!lp.Placed)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{msg}|NONPOD/{lp.Name}{lp.Index}");
                    return null;
                }
                return lp;
            }
            // 检查 LP 是否开门
            protected LoadPort CheckLoadPortOpened(Efem efem, SocketState socket, string param, out string cmd, out string msgOut, [CallerMemberName] string msg = null)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out cmd, out msgOut, msg);
                if (!lp.Opened)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{msg}|CLOSE/{lp.Name}{lp.Index}");
                    return null;
                }
                return lp;
            }

            // 检查执行结果
            protected bool CheckExecution(Efem efem, EfemComponent item, SocketState socket, string err)
            {
                if (!efem.running) return true;
                if (efem.ems)
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|EMS");
                    return true;
                }
                if (efem.hold)
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|HOLD");
                    return true;
                }
                if (efem.HasError(out string msg))
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|{msg}");
                    return true;
                }
                if (item.Moving)
                {
                    efem.SendResponse(socket.Socket, $"ABS:{err}|BUSY");
                    return true;
                }
                return false;
            }

            protected bool CheckParameterArm(Efem efem, SocketState socket, string arm, out byte armIdx, string msg)
            {
                armIdx = 1;
                switch (arm)
                {
                    case "ARM1":
                        break;
                    case "ARM2":
                        armIdx = 2;
                        break;
                    default:
                        efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                        return true;
                }
                return false;
            }
            protected bool CheckParameterStage(Efem efem, SocketState socket, string stage, out EfemComponent item, out Stages stageType, out byte idx, out byte slot, string msg)
            {
                item = null;
                slot = 1;
                idx = 0;
                stageType = Stages.Stage;
                switch (stage[0])
                {
                    case 'P':
                        stageType = Stages.LoadPort;
                        if (!byte.TryParse(stage.Substring(1, 1), out idx) || idx == 0 || idx > 2 || !byte.TryParse(stage.Substring(2), out slot) || slot == 0 || slot > 25)
                        {
                            efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                            return true;
                        }
                        item = efem.loadPorts[idx - 1];
                        break;
                    case 'A':
                        stageType = Stages.Aligner;
                        if (stage != "ALIGN1")
                        {
                            efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                            return true;
                        }
                        item = efem.aligner;
                        break;
                    case 'L':
                        if (!stage.StartsWith("LL") || (idx = (byte)(stage[2] - 'A')) > 2 || (stage.Length > 3 && (!byte.TryParse(stage.Substring(3), out slot) || slot != 1)))
                        {
                            efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                            return true;
                        }
                        idx++;
                        break;
                    default:
                        efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                        return true;
                }
                return false;
            }
            protected bool CheckParameters(Efem efem, SocketState socket, string param, string stage, string arm, out EfemComponent item, out Stages stageType, out byte idx, out byte slot, out byte armIdx, out string cmd, [CallerMemberName] string msg = null)
            {
                msg = $"{msg.Substring(3).ToUpper()}/{param}";
                cmd = msg;
                armIdx = 1;
                if (this.CheckParameterStage(efem, socket, stage, out item, out stageType, out idx, out slot, msg)) return true;
                //item = null;
                //slot = 1;
                //idx = 0;
                //stageType = Stages.Stage;
                //switch (stage[0])
                //{
                //    case 'P':
                //        stageType = Stages.LoadPort;
                //        if (!byte.TryParse(stage.Substring(1, 1), out idx) || idx == 0 || idx > 2 || !byte.TryParse(stage.Substring(2), out slot) || slot == 0 || slot > 25)
                //        {
                //            efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                //            return true;
                //        }
                //        item = efem.loadPorts[idx - 1];
                //        break;
                //    case 'A':
                //        stageType = Stages.Aligner;
                //        if (stage != "ALIGN1")
                //        {
                //            efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                //            return true;
                //        }
                //        item = efem.aligner;
                //        break;
                //    case 'L':
                //        if (!stage.StartsWith("LL") || (idx = (byte)(stage[2] - 'A')) > 2 || (stage.Length > 3 && (!byte.TryParse(stage.Substring(3), out slot) || slot != 1)))
                //        {
                //            efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                //            return true;
                //        }
                //        idx++;
                //        break;
                //    default:
                //        efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                //        return true;
                //}
                return this.CheckParameterArm(efem, socket, arm, out armIdx, msg);
                //switch (arm)
                //{
                //    case "ARM1":
                //        break;
                //    case "ARM2":
                //        armIdx = 2;
                //        break;
                //    default:
                //        efem.SendResponse(socket.Socket, $"CAN:{msg}|PARAM_NG");
                //        return true;
                //}
                return false;
            }
            protected bool CheckStageWafer(Efem efem, SocketState socket, EfemComponent item, byte arm, int slot, bool got, string cmd)
            {
                if (this.CheckExecution(efem, item, socket, cmd)) return true;
                string place;
                if (got == !item.Slots[slot - 1])
                {
                    place = item.Name == "L" ? $"LL{(char)('A' + item.Index - 1)}" : $"{item.Name}{item.Index}{slot:D2}";
                    efem.SendResponse(socket.Socket, $"CAN:{cmd}|{(got ? "NOWAFER" : "WAFER")}/{place}");
                    return true;
                }
                if (arm > 0 && got == efem.robot.Slots[arm - 1])
                {
                    place = $"ARM{arm}";
                    efem.SendResponse(socket.Socket, $"CAN:{cmd}|{(got ? "WAFER" : "NOWAFER")}/{place}");
                    return true;
                }
                return false;
            }
            protected bool CheckPickPlaceArgs(Efem efem, SocketState socket, bool got, string param, out Stages stage, out byte idx, out byte slot, out byte arm, out string msg, [CallerMemberName] string cmd = null)
            {
                string[] ps = param.Split('/');
                if (ps.Length != 2) throw new ArgumentException();
                if (this.CheckParameters(efem, socket, param, ps[0], ps[1], out EfemComponent item, out stage, out idx, out slot, out arm, out msg, cmd)) return true;

                cmd = cmd.Substring(3).ToUpper();
                if (this.CheckEfemItemOriginated(efem, efem.robot, socket, cmd, param)) return true;
                if (item == null)
                {
                    item = new EfemComponent("L") { Index = idx, Slots = efem.loadLocks };
                    slot = idx;
                }
                else
                {
                    if (item.Name[0] == 'P')
                    {
                        var lp = item as LoadPort;
                        if (!lp.Opened)
                        {
                            efem.SendResponse(socket.Socket, $"CAN:{msg}|CLOSE/P{item.Index}");
                            return true;
                        }
                    }
                }
                if (this.CheckStageWafer(efem, socket, item, arm, slot, got, msg)) return true;
                return false;
            }

            // 调用组件的方法
            protected bool ExecuteMethod(Efem efem, EfemComponent item, Func<Efem, bool> func, SocketState socket, string msg, string param)
            {
                string err = this.Concat(msg, param);
                if (func(efem))
                {
                    this.CheckExecution(efem, item, socket, err);
                    return true;
                }
                if (efem.abort)
                {
                }
                return false;
            }
        }

        class MovAnalyser : BaseAnalyser
        {
            void MovInit(Efem efem, SocketState socket, string param)
            {
                string msg = "INIT";
                if (param == null || param == "ALL")
                {
                    var items = this.GetEfemItems(efem);
                    efem.SendResponse(socket.Socket, param == null ? "ACK:INIT" : "ACK:INIT/ALL");
                    foreach (var item in items)
                    {
                        if (this.ExecuteMethod(efem, item, item.Initialize, socket, msg, param)) return;
                    }
                    efem.SendResponse(socket.Socket, param == null ? "INF:INIT" : "INF:INIT/ALL");
                    return;
                }

                efem.SendResponse(socket.Socket, $"ACK:INIT/{param}");
                if (!byte.TryParse(param.Substring(param.Length - 1), out byte idx)) goto nak;
                switch (param.Substring(0, param.Length - 1))
                {
                    case "P":
                        switch (idx)
                        {
                            case 1:
                            case 2:
                                var lp = efem.loadPorts[idx - 1];
                                if (this.ExecuteMethod(efem, lp, lp.Initialize, socket, msg, param)) return;
                                goto inf;
                        }
                        break;
                    case "ROBOT":
                        if (idx == 1)
                        {
                            if (this.ExecuteMethod(efem, efem.robot, efem.robot.Initialize, socket, msg, param)) return;
                            goto inf;
                        }
                        break;
                    case "ALIGN":
                        if (idx == 1)
                        {
                            if (this.ExecuteMethod(efem, efem.aligner, efem.aligner.Initialize, socket, msg, param)) return;
                            goto inf;
                        }
                        break;
                    default:
                        goto nak;
                }
                efem.SendResponse(socket.Socket, $"CAN:INIT/{param}|REMOVE/{param}");
                return;
            nak:
                efem.SendResponse(socket.Socket, $"NAK:PARAM_NG|INIT/{param}");
                return;
            inf:
                efem.SendResponse(socket.Socket, $"INF:INIT/{param}");
            }
            void MovOrgsh(Efem efem, SocketState socket, string param)
            {
                string msg = "ORGSH";
                if (param == null || param == "ALL")
                {
                    var items = this.GetEfemItems(efem);
                    foreach (var item in items)
                    {
                        if (this.CheckEfemInitialized(efem, item, socket, msg, param)) return;
                    }
                    efem.SendResponse(socket.Socket, param == null ? "ACK:ORGSH" : "ACK:ORGSH/ALL");
                    foreach (var item in items)
                    {
                        if (this.ExecuteMethod(efem, item, item.Originate, socket, msg, param)) return;
                    }
                    efem.SendResponse(socket.Socket, param == null ? "INF:ORGSH" : "INF:ORGSH/ALL");
                    return;
                }

                efem.SendResponse(socket.Socket, $"ACK:ORGSH/{param}");
                if (!byte.TryParse(param.Substring(param.Length - 1), out byte idx)) goto nak;
                switch (param.Substring(0, param.Length - 1))
                {
                    case "P":
                        switch (idx)
                        {
                            case 1:
                            case 2:
                                var lp = efem.loadPorts[idx - 1];
                                if (this.CheckEfemInitialized(efem, lp, socket, msg, param)) return;
                                if (this.ExecuteMethod(efem, lp, lp.Originate, socket, msg, param)) return;
                                goto inf;
                        }
                        break;
                    case "ROBOT":
                        if (idx == 1)
                        {
                            if (this.CheckEfemInitialized(efem, efem.robot, socket, msg, param)) return;
                            if (this.ExecuteMethod(efem, efem.robot, efem.robot.Originate, socket, msg, param)) return;
                            goto inf;
                        }
                        break;
                    case "ALIGN":
                        if (idx == 1)
                        {
                            if (this.CheckEfemInitialized(efem, efem.aligner, socket, msg, param)) return;
                            if (this.ExecuteMethod(efem, efem.aligner, efem.aligner.Originate, socket, msg, param)) return;
                            goto inf;
                        }
                        break;
                    default:
                        goto nak;
                }
                efem.SendResponse(socket.Socket, $"CAN:ORGSH/{param}|REMOVE/{param}");
                return;
            nak:
                efem.SendResponse(socket.Socket, $"NAK:PARAM_NG|ORGSH/{param}");
                return;
            inf:
                efem.SendResponse(socket.Socket, $"INF:ORGSH/{param}");
            }
            void MovLock(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Lock, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
            void MovUnlock(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Unlock, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
            void MovDock(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Dock, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
            void MovUndock(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Undock, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
            void MovOpen(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Open, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
            void MovClose(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Close, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
            void MovWafsh(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOpened(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Wafsh, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
            void MovGoto(Efem efem, SocketState socket, string param)
            {
                string[] ps = param.Split('/');
                if (ps.Length != 3 || (ps[2] != "UP" && ps[2] != "DOWN")) throw new ArgumentException();
                if (this.CheckParameters(efem, socket, param, ps[0], ps[1], out _, out Stages stage, out byte idx, out _, out byte arm, out string msg)) return;

                if (this.CheckEfemItemOriginated(efem, efem.robot, socket, "GOTO", param)) return;

                if (efem.moving)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{msg}|BUSY");
                    return;
                }
                efem.moving = true;

                efem.SendResponse(socket.Socket, $"ACK:{msg}");
                switch (stage)
                {
                    case Stages.Aligner:
                        if (efem.robot.GotoAligner(efem, idx, arm)) return;
                        break;
                    case Stages.LoadPort:
                        if (efem.robot.GotoLoadPort(efem, idx, arm)) return;
                        break;
                    default:
                        if (efem.robot.GotoStage(efem, idx, arm)) return;
                        break;
                }
                efem.SendResponse(socket.Socket, $"INF:{msg}");
                efem.moving = false;
            }
            void MovLoad(Efem efem, SocketState socket, string param)
            {
                if (this.CheckPickPlaceArgs(efem, socket, true, param, out Stages stage, out byte idx, out byte slot, out byte arm, out string msg)) return;

                if (efem.moving)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{msg}|BUSY");
                    return;
                }
                efem.moving = true;

                efem.SendResponse(socket.Socket, $"ACK:{msg}");
                if (efem.robot.Pick(efem, arm, stage, idx, slot)) return;
                efem.SendResponse(socket.Socket, $"INF:{msg}");
                efem.moving = false;
            }
            void MovUnload(Efem efem, SocketState socket, string param)
            {
                if (this.CheckPickPlaceArgs(efem, socket, false, param, out Stages stage, out byte idx, out byte slot, out byte arm, out string msg)) return;

                if (efem.moving)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{msg}|BUSY");
                    return;
                }
                efem.moving = true;

                efem.SendResponse(socket.Socket, $"ACK:{msg}");
                if (efem.robot.Place(efem, arm, stage, idx, slot)) return;
                efem.SendResponse(socket.Socket, $"INF:{msg}");
                efem.moving = false;
            }
            void MovTrans(Efem efem, SocketState socket, string param)
            {
                string[] ps = param.Split('/');
                if (ps.Length != 2) throw new ArgumentException();
                string[] l = ps[0].Split('>');
                string[] r = ps[1].Split('>');
                if (l.Length != 2 || r.Length != 2) throw new ArgumentException();
                if (this.CheckParameters(efem, socket, param, l[0], l[1], out EfemComponent item1, out Stages stage1, out byte idx1, out byte slot1, out byte arm1, out string msg)) return;
                if (this.CheckParameters(efem, socket, param, r[1], r[0], out EfemComponent item2, out Stages stage2, out byte idx2, out byte slot2, out byte arm2, out _)) return;
                if (item1 != null && this.CheckEfemItemOriginated(efem, item1, socket, "TRANS", param)) return;
                if (item2 != null && this.CheckEfemItemOriginated(efem, item2, socket, "TRANS", param)) return;
                if (this.CheckEfemItemOriginated(efem, efem.robot, socket, "TRANS", param)) return;

                if (efem.moving)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{msg}|BUSY");
                    return;
                }
                efem.moving = true;

                if (item1 == null)
                {
                    item1 = new EfemComponent("L") { Index = idx1, Slots = efem.loadLocks };
                    slot1 = idx1;
                }
                else
                {
                    if (item1.Name[0] == 'P')
                    {
                        var lp = item1 as LoadPort;
                        if (!lp.Opened)
                        {
                            efem.SendResponse(socket.Socket, $"CAN:{msg}|CLOSE/P{item1.Index}");
                            return;
                        }
                    }
                }
                if (item2 == null)
                {
                    item2 = new EfemComponent("L") { Index = idx2, Slots = efem.loadLocks };
                    slot2 = idx2;
                }
                else
                {
                    if (item2.Name[0] == 'P')
                    {
                        var lp = item2 as LoadPort;
                        if (!lp.Opened)
                        {
                            efem.SendResponse(socket.Socket, $"CAN:{msg}|CLOSE/P{item2.Index}");
                            return;
                        }
                    }
                }
                if (this.CheckStageWafer(efem, socket, item1, arm1, slot1, true, msg)) return;
                if (this.CheckStageWafer(efem, socket, item2, arm2 == arm1 ? (byte)0 : arm2, slot2, false, msg)) return;

                efem.SendResponse(socket.Socket, $"ACK:{msg}");
                if (efem.robot.Pick(efem, arm1, stage1, idx1, slot1)) return;
                if (efem.robot.Place(efem, arm2, stage2, idx2, slot2)) return;
                efem.SendResponse(socket.Socket, $"INF:{msg}");
                efem.moving = false;
            }
            void MovChange(Efem efem, SocketState socket, string param)
            {
                string[] ps = param.Split('/');
                if (ps.Length != 3) throw new ArgumentException();
                if (this.CheckParameters(efem, socket, param, ps[0], ps[1], out EfemComponent item1, out Stages stage1, out byte idx1, out byte slot1, out byte arm1, out string msg)) return;
                if (this.CheckParameterArm(efem, socket, ps[2], out byte arm2, msg)) return;
                if (item1 != null && this.CheckEfemItemOriginated(efem, item1, socket, "TRANS", param)) return;
                if (this.CheckEfemItemOriginated(efem, efem.robot, socket, "TRANS", param)) return;

                if (efem.moving)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{msg}|BUSY");
                    return;
                }
                efem.moving = true;

                if (item1 == null)
                {
                    item1 = new EfemComponent("L") { Index = idx1, Slots = efem.loadLocks };
                    slot1 = idx1;
                }
                else
                {
                    if (item1.Name[0] == 'P')
                    {
                        var lp = item1 as LoadPort;
                        if (!lp.Opened)
                        {
                            efem.SendResponse(socket.Socket, $"CAN:{msg}|CLOSE/P{item1.Index}");
                            return;
                        }
                    }
                }
                if (this.CheckStageWafer(efem, socket, item1, arm1, slot1, true, msg)) return;

                efem.SendResponse(socket.Socket, $"ACK:{msg}");
                if (efem.robot.Pick(efem, arm1, stage1, idx1, slot1)) return;
                if (efem.robot.Place(efem, arm2, stage1, idx1, slot1)) return;
                efem.SendResponse(socket.Socket, $"INF:{msg}");
                efem.moving = false;
            }
            void MovAlign(Efem efem, SocketState socket, string param)
            {
                if (param != "ALIGN1") throw new Exception();
                efem.SendResponse(socket.Socket, $"ACK:ALIGN/ALIGN1");
                if (this.ExecuteMethod(efem, efem.aligner, efem.aligner.Align, socket, "ALIGN", param)) return;
                efem.SendResponse(socket.Socket, $"INF:ALIGN/ALIGN1");
            }
            void MovWid(Efem efem, SocketState socket, string param)
            {
                var ps = param.Split('/');
                if (ps[0] != "ALIGN1" || !ps[1].StartsWith("WIDRecipe") || !ushort.TryParse(ps[1].Substring(9), out ushort wid) || wid > 399) throw new Exception();
                if (ps.Length == 3 && (ps[2] != "0" || ps[2] != "1")) throw new Exception();
                if (!efem.aligner.HasWafer)
                {
                    efem.SendResponse(socket.Socket, $"CAN:WID/{param}|NONWAF|ALIGN1");
                    return;
                }
                efem.SendResponse(socket.Socket, $"ACK:WID/{param}");
                efem.SendResponse(socket.Socket, $"INF:WID/{param}/{efem.aligner.GetWaferID()};520;.\\mybeautifulgirl.png");
            }
            void MovHome(Efem efem, SocketState socket, string param)
            {
                EfemComponent item;
                switch (param.Substring(0, param.Length - 1))
                {
                    case "P":
                        if (!byte.TryParse(param.Substring(1), out byte idx) || idx < 1 || idx > 2)
                        {
                            efem.SendResponse(socket.Socket, $"NAK:HOME/{param}|REMOVED/{param}");
                            return;
                        }
                        item = efem.loadPorts[idx - 1];
                        break;
                    case "ROBOT":
                        if (param[param.Length - 1] != '1')
                        {
                            efem.SendResponse(socket.Socket, $"NAK:HOME/{param}|REMOVED/{param}");
                            return;
                        }
                        item = efem.robot;
                        break;
                    default:
                        throw new Exception();
                }
                if (!this.CheckEfemInitialized(efem, item, socket, "HOME", param)) return;
                efem.SendResponse(socket.Socket, $"ACK:HOME/{param}");
                if (this.ExecuteMethod(efem, item, item.Originate, socket, "HOME", param)) return;
                efem.SendResponse(socket.Socket, $"INF:HOME/{param}");
            }
            void MovHold(Efem efem, SocketState socket, string param)
            {
                if (param != null)
                {
                    efem.SendResponse(socket.Socket, $"NAK:HOLD/{param}|PARAM_NG");
                    return;
                }
                if (!efem.robot.Moving)
                {
                    efem.SendResponse(socket.Socket, $"CAN:HOLD|NOTINMOTION");
                    return;
                }
                efem.hold = true;
                efem.SendResponse(socket.Socket, $"INF:HOLD");
            }
            void MovRestr(Efem efem, SocketState socket, string param)
            {
                if (param != null)
                {
                    efem.SendResponse(socket.Socket, $"NAK:RESTR/{param}|PARAM_NG");
                    return;
                }
                if (!efem.hold)
                {
                    efem.SendResponse(socket.Socket, $"CAN:RESTR|NOHOLD");
                    return;
                }
                efem.hold = false;
                efem.SendResponse(socket.Socket, $"INF:RESTR");
            }
            void MovAbort(Efem efem, SocketState socket, string param)
            {
                if (param != null)
                {
                    efem.SendResponse(socket.Socket, $"NAK:ABORT/{param}|PARAM_NG");
                    return;
                }
                if (!efem.hold)
                {
                    efem.SendResponse(socket.Socket, $"CAN:ABORT|NOHOLD");
                    return;
                }
                efem.hold = false;
                efem.abort = true;
                efem.SendResponse(socket.Socket, $"INF:ABORT");
            }
            void MovEms(Efem efem, SocketState socket, string param)
            {
                if (param != null)
                {
                    efem.SendResponse(socket.Socket, $"NAK:EMS/{param}|PARAM_NG");
                    return;
                }
                efem.ems = true;
                efem.SendResponse(socket.Socket, $"INF:EMS");
            }
            void MovTransreq(Efem efem, SocketState socket, string param) { }
            void MovAdpLock(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                if (!lp.HasAdapter)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{err}|NONADP/{lp.Name}{lp.Index}");
                    return;
                }
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Lock, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
            void MovAdpUnlock(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOriginated(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                if (!lp.HasAdapter)
                {
                    efem.SendResponse(socket.Socket, $"CAN:{err}|NONADP/{lp.Name}{lp.Index}");
                    return;
                }
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Unlock, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}");
            }
        }
        class GetAnalyser : BaseAnalyser
        {
            void GetMapdt(Efem efem, SocketState socket, string param)
            {
                var lp = this.CheckLoadPortOpened(efem, socket, param, out string err, out string msg);
                if (lp == null) return;
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                if (this.ExecuteMethod(efem, lp, lp.Wafsh, socket, msg, param)) return;
                efem.SendResponse(socket.Socket, $"INF:{err}/{string.Join("", from i in lp.Slots select i ? "1" : "0")}");
            }
            void GetError(Efem efem, SocketState socket, string param) { }
            void GetClamp(Efem efem, SocketState socket, string param)
            {
                bool clamp;
                switch (param.Substring(0, param.Length - 1))
                {
                    case "ARM":
                        int idx = param[3] - '1';
                        if (idx < 0 || idx > 1) throw new Exception();
                        clamp = efem.robot.clamps[idx];
                        break;
                    case "ALIGN":
                        if (param[5] != '1') throw new Exception();
                        clamp = efem.aligner.Clamped;
                        break;
                    default: throw new Exception();
                }
                string err = this.Concat("CLAMP", param);
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                efem.SendResponse(socket.Socket, $"INFO:{err}/O{(clamp ? "N" : "FF")}");
            }
            void GetState(Efem efem, SocketState socket, string param)
            {
                string data;
                switch (param)
                {
                    case "VER":
                        data = "v2022/03/24 by wangfs763";
                        break;
                    case "TRACK":
                        data = $"{(efem.robot.Wafer1 ? "300" : "NONE")}/{(efem.robot.Wafer2 ? "300" : "NONE")}/{(efem.aligner.HasWafer ? "300" : "NONE")}";
                        break;
                    //case "":
                    //    break;
                    //case "":
                    //    break;
                    //case "":
                    //    break;
                    default: throw new Exception();
                }
                string err = this.Concat("STATE", param);
                efem.SendResponse(socket.Socket, $"ACK:{err}");
                efem.SendResponse(socket.Socket, $"INFO:{err}/{data}");
            }
            void GetMode(Efem efem, SocketState socket, string param) { }
            void GetTransreq(Efem efem, SocketState socket, string param) { }
            void GetSigstat(Efem efem, SocketState socket, string param) { }
            void GetEvent(Efem efem, SocketState socket, string param) { }
            void GetCstid(Efem efem, SocketState socket, string param) { }
            void GetPurge(Efem efem, SocketState socket, string param) { }
            void GetSize(Efem efem, SocketState socket, string param) { }
        }
        class SetAnalyser : BaseAnalyser
        {
            void SetAlign(Efem efem, SocketState socket, string param)
            {
                var ps = param.Split('/');
                if (ps[0] != "ALIGN1") throw new Exception();
                switch (ps[1][0])
                {
                    case 'P':
                        if (!byte.TryParse(ps[1].Substring(1), out byte idx) || idx == 0 || idx > 2)
                        {
                            efem.SendResponse(socket.Socket, $"NAK:ALIGN/{param}|REMOVE/{ps[1]}");
                            return;
                        }
                        break;
                    case 'B':
                        efem.SendResponse(socket.Socket, $"NAK:ALIGN/{param}|NOFUNC/{ps[1]}");
                        return;
                    case 'L':
                        if (ps[1].Length != 3 || ps[1][1] != 'A' || ps[1][2] < 'A' || ps[1][2] > 'B')
                        {
                            efem.SendResponse(socket.Socket, $"NAK:ALIGN/{param}|REMOVE/{ps[1]}");
                            return;
                        }
                        break;
                    case 'D':
                        if (!float.TryParse(ps[1].Substring(1), out float angle))
                        {
                            efem.SendResponse(socket.Socket, $"NAK:ALIGN/{param}|PARAM_NG/{ps[1]}");
                            return;
                        }
                        efem.aligner.SetAngle(angle);
                        break;
                    default:
                        throw new Exception();
                }
                efem.SendResponse(socket.Socket, $"ACK:ALIGN/ALIGN1");
            }
            void SetError(Efem efem, SocketState socket, string param) { }
            void SetClamp(Efem efem, SocketState socket, string param)
            {
                var ps = param.Split('/');
                if (ps.Length != 2 || (ps[1] != "ON" && ps[1] != "OFF")) throw new Exception();
                switch (param.Substring(0, param.Length - 1))
                {
                    case "ARM":
                        int idx = param[3] - '1';
                        if (idx < 0 || idx > 1) throw new Exception();
                        efem.robot.clamps[idx] = ps[1][1] == 'N';
                        break;
                    case "ALIGN":
                        if (param[5] != '1') throw new Exception();
                        efem.aligner.Clamped = ps[1][1] == 'N';
                        break;
                    default: throw new Exception();
                }
                string err = this.Concat("CLAMP", param);
                efem.SendResponse(socket.Socket, $"ACK:{err}");
            }
            void SetMode(Efem efem, SocketState socket, string param) { }
            void SetSigOut(Efem efem, SocketState socket, string param) { }
            void SetEvent(Efem efem, SocketState socket, string param) { }
            void SetPurge(Efem efem, SocketState socket, string param) { }
            void SetSize(Efem efem, SocketState socket, string param) { }
        }
        class AckAnalyser : BaseAnalyser
        {
            protected override bool CheckCommunication(Efem efem, SocketState socket, string msgParam) => false;

            protected override void Analysis(Efem efem, SocketState socket, string msg, string arg)
            {
                if (msg == "READY")
                {
                    this.AckReady(efem, socket, arg);
                    return;
                }

                if (this.CheckCommunication(efem, socket, arg == null ? msg : string.Concat(msg, "/", arg))) return;
                try { base.Analysis(efem, socket, msg, arg); }
                catch { }
            }

            void AckReady(Efem efem, SocketState socket, string param)
            {
                if (param == "COMM")
                {
                    socket.Established = true;
                    efem.OnComEstablished();
                    return;
                }
                efem.SendResponse(socket.Socket, $"NAK:PARAM_NG/READY/{param}|");
            }
        }

        #endregion
    }

    #region 事件委托

    delegate void StoppedEventHandler(bool errorOrStop);
    delegate void MessageArrivedEventHandler(bool? sent, string msg);
    delegate void SystemIoChangedEventHandler(SystemInputIO input, SystemOutputIO output);

    //delegate void LoadPortInitializedEventHandler(int index);
    //delegate void LoadPortOriginatedEventHandler(int index);
    delegate void LoadPortLockedEventHandler(int index, bool locked);
    delegate void LoadPortDockedEventHandler(int index, bool docked);
    delegate void LoadPortDockingEventHandler(int index, bool dock, float rate);
    delegate void LoadPortOpenedEventHandler(int index, bool opened);
    delegate void LoadPortOpeningEventHandler(int index, bool open, float rate);
    delegate void LoadPortMappingChangedEventHandler(int index, bool[] slots);
    delegate void LoadPortIoChangedEventHandler(int index, LoadPortInputIO input, LoadPortOutputIO output);

    //delegate void AlignerInitializedEventHandler();
    //delegate void AlignerOriginatedEventHandler();
    delegate void AlignerMovingEventHandler(float rate);
    delegate void AlignerRotatingEventHandler(float angle);

    delegate void AxisYMovedEventHandler(bool wafer1, bool wafer2, float rate);
    //delegate void MoveToLLAEventHandler(bool wafer1, bool wafer2, float rate);
    //delegate void MoveToLPEventHandler(bool wafer1, bool wafer2, float rate);

    delegate void RobotGotoLoadPortEventHandler(int arm, bool wafer1, bool wafer2, float rate);
    delegate void ArmGoInsideLoadPortEventHandler(int lp, int arm, bool wafer1, bool wafer2, float rate);
    delegate void LoadPortWaferGotEventHandler(int lp, int slot, int arm, bool got);

    delegate void RobotGotoAlignerEventHandler(int arm, bool wafer1, bool wafer2, float rate);
    delegate void ArmGoInsideAlignerEventHandler(int arm, bool wafer1, bool wafer2, float rate);
    delegate void AlignerWaferGotEventHandler(int arm, bool got);

    delegate void RobotGotoStageEventHandler(int stage, int arm, bool wafer1, bool wafer2, float rate);
    delegate void ArmGoInsideStageEventHandler(int stage, int arm, bool wafer1, bool wafer2, float rate);
    delegate void StageWaferGotEventHandler(int stage, int arm, bool got);

    #endregion

    #region IO 枚举 2022.1.20

    [Flags]
    enum SystemInputIO
    {
        None,

        VacSrcPressure1 = 1,
        VacSrcPressure2 = 2,
        CompAirPressure1 = 4,
        CompAirPressure2 = 8,
        DiffPressSensor1 = 16,
        DiffPressSensor2 = 32,
        FfuNormal = 64,
        IonizerNormal = 128,
        RunMode = 256,
        DrivePower = 512,
        DoorClosed = 1024,
        AreaSensor_BarInterLock = 2048,
    }

    [Flags]
    enum LoadPortInputIO
    {
        None,

        CarrierPlaced = 1,
        CarrierPresence = 2,
        AccessSWPressed = 4,
        CarrierLocked = 8,

        CoverClosed = 16,
        CoverLocked = 32,
        DoorOpened = 128,

        InfoPadAPressed = 256,
        InfoPadBPressed = 512,
        InfoPadCPressed = 1024,
        InfoPadDPressed = 2048,

        AdapterPresence = 0x1000,
        AdapterConnected = 0x2000,
        AdapterValid = 0x4000,

        N2PurgeProcessing = 0x10000,

        E84_Valid = 0x1000000,
        E84_CS0 = 0x2000000,
        E84_CS1 = 0x4000000,
        E84_Resv = 0x8000000,
        E84_TR_REQ = 0x10000000,
        E84_Busy = 0x20000000,
        E84_Compt = 0x40000000,
        E84_Cont = -2147483648,
    }

    [Flags]
    enum SystemOutputIO
    {
        None,

        RedON = 1,
        YellowON = 2,
        GreenON = 4,
        BlueON = 8,
        WhiteON = 16,
        RedFlash = 32,
        YellowFlash = 64,
        GreenFlash = 128,
        BlueFlash = 256,
        WhiteFlash = 512,
        Buzzer1 = 1024,
        Buzzer2 = 2048,
    }

    [Flags]
    enum LoadPortOutputIO
    {
        None,

        Presence = 1,
        Placement = 2,
        Load = 4,
        Unload = 8,

        ManualMode = 16,
        Error = 32,
        Clamp = 64,
        Dock = 128,
        AccessSW = 256,

        E84_L_REQ = 0x1000000,
        E84_U_REQ = 0x2000000,
        E84_Resv1 = 0x4000000,
        E84_Ready = 0x8000000,
        E84_Resv2 = 0x10000000,
        E84_Resv3 = 0x20000000,
        E84_HO_AVBL = 0x40000000,
        E84_ES = -2147483648,
    }

    #endregion

}
